#include <VkGraphics.hpp>
#include <Common.hpp>
#include <vulkan/vulkan.hpp>

#define APP_USE_VULKAN_DEBUG_REPORT

namespace NaturalEngine {
    // region Vulkan Debug Report
    const std::unordered_map<VkResult, cstr> VkResultTxtMap = {
            {VK_SUCCESS,                        "成功 (Success)"},
            {VK_NOT_READY,                      "未准备好 (Not Ready)"},
            {VK_TIMEOUT,                        "超时 (Timeout)"},
            {VK_EVENT_SET,                      "事件已设置 (Event Set)"},
            {VK_EVENT_RESET,                    "事件已重置 (Event Reset)"},
            {VK_INCOMPLETE,                     "不完整 (Incomplete)"},
            {VK_ERROR_OUT_OF_HOST_MEMORY,       "主机内存不足 (Out Of Host Memory)"},
            {VK_ERROR_OUT_OF_DEVICE_MEMORY,     "设备内存不足 (Out Of Device Memory)"},
            {VK_ERROR_INITIALIZATION_FAILED,    "初始化失败 (Initialization Failed)"},
            {VK_ERROR_DEVICE_LOST,              "设备丢失 (Device Lost)"},
            {VK_ERROR_MEMORY_MAP_FAILED,        "内存映射失败 (Memory Map Failed)"},
            {VK_ERROR_LAYER_NOT_PRESENT,        "层不存在 (Layer Not Present)"},
            {VK_ERROR_EXTENSION_NOT_PRESENT,    "扩展不存在 (Extension Not Present)"},
            {VK_ERROR_FEATURE_NOT_PRESENT,      "特性不存在 (Feature Not Present)"},
            {VK_ERROR_INCOMPATIBLE_DRIVER,      "驱动不兼容 (Incompatible Driver)"},
            {VK_ERROR_TOO_MANY_OBJECTS,         "对象过多 (Too Many Objects)"},
            {VK_ERROR_FORMAT_NOT_SUPPORTED,     "格式不支持 (Format Not Supported)"},
            {VK_ERROR_FRAGMENTED_POOL,          "池碎片化 (Fragmented Pool)"},
            {VK_ERROR_OUT_OF_POOL_MEMORY,       "池内存不足 (Out Of Pool Memory)"},
            {VK_ERROR_INVALID_EXTERNAL_HANDLE,  "无效的外部句柄 (Invalid External Handle)"},
            {VK_ERROR_SURFACE_LOST_KHR,         "表面丢失 (Surface Lost KHR)"},
            {VK_ERROR_NATIVE_WINDOW_IN_USE_KHR, "本地窗口正在使用 (Native Window In Use KHR)"},
            {VK_SUBOPTIMAL_KHR,                 "次优 (Suboptimal KHR)"},
            {VK_ERROR_OUT_OF_DATE_KHR,          "过期 (Out Of Date KHR)"},
            {VK_ERROR_INCOMPATIBLE_DISPLAY_KHR, "显示不兼容 (Incompatible Display KHR)"},
            {VK_ERROR_VALIDATION_FAILED_EXT,    "验证失败 (Validation Failed EXT)"},
            {VK_ERROR_INVALID_SHADER_NV,        "无效的着色器 (Invalid Shader NV)"},
            {VK_ERROR_NOT_PERMITTED_EXT,        "不允许 (Not Permitted EXT)"}
    };

    inline static cstr GetResultExt(VkResult err) {
        auto iter = VkResultTxtMap.find(err);
        if (iter != VkResultTxtMap.end())
            return iter->second;
        return "未知错误 (Unknown Error)";
    }

    const std::unordered_map<VkDebugReportObjectTypeEXT, cstr> VkDebugReportObjectTypeEXTMap = {
            {VK_DEBUG_REPORT_OBJECT_TYPE_UNKNOWN_EXT,                        "未知对象类型 (Unknown)"},
            {VK_DEBUG_REPORT_OBJECT_TYPE_INSTANCE_EXT,                       "实例 (Instance)"},
            {VK_DEBUG_REPORT_OBJECT_TYPE_PHYSICAL_DEVICE_EXT,                "物理设备 (Physical Device)"},
            {VK_DEBUG_REPORT_OBJECT_TYPE_DEVICE_EXT,                         "设备 (Device)"},
            {VK_DEBUG_REPORT_OBJECT_TYPE_QUEUE_EXT,                          "队列 (Queue)"},
            {VK_DEBUG_REPORT_OBJECT_TYPE_SEMAPHORE_EXT,                      "信号量 (Semaphore)"},
            {VK_DEBUG_REPORT_OBJECT_TYPE_COMMAND_BUFFER_EXT,                 "命令缓冲区 (Command Buffer)"},
            {VK_DEBUG_REPORT_OBJECT_TYPE_FENCE_EXT,                          "栅栏 (Fence)"},
            {VK_DEBUG_REPORT_OBJECT_TYPE_DEVICE_MEMORY_EXT,                  "设备内存 (Device Memory)"},
            {VK_DEBUG_REPORT_OBJECT_TYPE_BUFFER_EXT,                         "缓冲区 (Buffer)"},
            {VK_DEBUG_REPORT_OBJECT_TYPE_IMAGE_EXT,                          "图像 (Image)"},
            {VK_DEBUG_REPORT_OBJECT_TYPE_EVENT_EXT,                          "事件 (Event)"},
            {VK_DEBUG_REPORT_OBJECT_TYPE_QUERY_POOL_EXT,                     "查询池 (Query Pool)"},
            {VK_DEBUG_REPORT_OBJECT_TYPE_BUFFER_VIEW_EXT,                    "缓冲区视图 (Buffer View)"},
            {VK_DEBUG_REPORT_OBJECT_TYPE_IMAGE_VIEW_EXT,                     "图像视图 (Image View)"},
            {VK_DEBUG_REPORT_OBJECT_TYPE_SHADER_MODULE_EXT,                  "着色器模块 (Shader Module)"},
            {VK_DEBUG_REPORT_OBJECT_TYPE_PIPELINE_CACHE_EXT,                 "管道缓存 (Pipeline Cache)"},
            {VK_DEBUG_REPORT_OBJECT_TYPE_PIPELINE_LAYOUT_EXT,                "管道布局 (Pipeline Layout)"},
            {VK_DEBUG_REPORT_OBJECT_TYPE_RENDER_PASS_EXT,                    "渲染通道 (Render Pass)"},
            {VK_DEBUG_REPORT_OBJECT_TYPE_PIPELINE_EXT,                       "管道 (Pipeline)"},
            {VK_DEBUG_REPORT_OBJECT_TYPE_DESCRIPTOR_SET_LAYOUT_EXT,          "描述符集布局 (Descriptor Set Layout)"},
            {VK_DEBUG_REPORT_OBJECT_TYPE_SAMPLER_EXT,                        "采样器 (Sampler)"},
            {VK_DEBUG_REPORT_OBJECT_TYPE_DESCRIPTOR_POOL_EXT,                "描述符池 (Descriptor Pool)"},
            {VK_DEBUG_REPORT_OBJECT_TYPE_DESCRIPTOR_SET_EXT,                 "描述符集 (Descriptor Set)"},
            {VK_DEBUG_REPORT_OBJECT_TYPE_FRAMEBUFFER_EXT,                    "帧缓冲区 (Framebuffer)"},
            {VK_DEBUG_REPORT_OBJECT_TYPE_COMMAND_POOL_EXT,                   "命令池 (Command Pool)"},
            {VK_DEBUG_REPORT_OBJECT_TYPE_SURFACE_KHR_EXT,                    "表面 (Surface)"},
            {VK_DEBUG_REPORT_OBJECT_TYPE_SWAPCHAIN_KHR_EXT,                  "交换链 (Swapchain)"},
            {VK_DEBUG_REPORT_OBJECT_TYPE_DEBUG_REPORT_CALLBACK_EXT_EXT,      "调试报告回调 (Debug Report Callback)"},
            {VK_DEBUG_REPORT_OBJECT_TYPE_DISPLAY_KHR_EXT,                    "显示器 (Display)"},
            {VK_DEBUG_REPORT_OBJECT_TYPE_DISPLAY_MODE_KHR_EXT,               "显示模式 (Display Mode)"},
            {VK_DEBUG_REPORT_OBJECT_TYPE_VALIDATION_CACHE_EXT_EXT,           "验证缓存 (Validation Cache)"},
            {VK_DEBUG_REPORT_OBJECT_TYPE_SAMPLER_YCBCR_CONVERSION_EXT,       "采样器YCBCR转换 (Sampler YCBCR Conversion)"},
            {VK_DEBUG_REPORT_OBJECT_TYPE_DESCRIPTOR_UPDATE_TEMPLATE_EXT,     "描述符更新模板 (Descriptor Update Template)"},
            {VK_DEBUG_REPORT_OBJECT_TYPE_CU_MODULE_NVX_EXT,                  "CU模块 (CU Module)"},
            {VK_DEBUG_REPORT_OBJECT_TYPE_CU_FUNCTION_NVX_EXT,                "CU函数 (CU Function)"},
            {VK_DEBUG_REPORT_OBJECT_TYPE_ACCELERATION_STRUCTURE_KHR_EXT,     "加速结构 (Acceleration Structure KHR)"},
            {VK_DEBUG_REPORT_OBJECT_TYPE_ACCELERATION_STRUCTURE_NV_EXT,      "加速结构 (Acceleration Structure NV)"},
            {VK_DEBUG_REPORT_OBJECT_TYPE_CUDA_MODULE_NV_EXT,                 "CUDA模块 (CUDA Module)"},
            {VK_DEBUG_REPORT_OBJECT_TYPE_CUDA_FUNCTION_NV_EXT,               "CUDA函数 (CUDA Function)"},
            {VK_DEBUG_REPORT_OBJECT_TYPE_BUFFER_COLLECTION_FUCHSIA_EXT,      "缓冲区集合 (Buffer Collection Fuchsia)"},
            {VK_DEBUG_REPORT_OBJECT_TYPE_DEBUG_REPORT_EXT,                   "调试报告 (Debug Report)"},
            {VK_DEBUG_REPORT_OBJECT_TYPE_VALIDATION_CACHE_EXT,               "验证缓存 (Validation Cache)"},
            {VK_DEBUG_REPORT_OBJECT_TYPE_DESCRIPTOR_UPDATE_TEMPLATE_KHR_EXT, "描述符更新模板 (Descriptor Update Template KHR)"},
            {VK_DEBUG_REPORT_OBJECT_TYPE_SAMPLER_YCBCR_CONVERSION_KHR_EXT,   "采样器YCBCR转换 (Sampler YCBCR Conversion KHR)"}
    };

    inline static cstr GetDebugReportObjectTypeTxt(VkDebugReportObjectTypeEXT objectType) {
        auto iter = VkDebugReportObjectTypeEXTMap.find(objectType);
        if (iter != VkDebugReportObjectTypeEXTMap.end())
            return iter->second;
        return "未知对象类型 (Unknown)";
    }
// endregion


    cstr GetDeviceType(VkPhysicalDeviceType deviceType) {
        if (deviceType == VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU)
            return "Integrated GPU";
        else if (deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU)
            return "Descrete GPU";
        else if (deviceType == VK_PHYSICAL_DEVICE_TYPE_VIRTUAL_GPU)
            return "Virtual GPU";
        else if (deviceType == VK_PHYSICAL_DEVICE_TYPE_CPU)
            return "CPU";
        return "Unknown";
    }

    static VKAPI_ATTR VkBool32 VKAPI_PTR HandleDebugReport(VkDebugReportFlagsEXT flags,
                                                           VkDebugReportObjectTypeEXT objectType,
                                                           uint64_t object,
                                                           size_t location,
                                                           int32_t messageCode,
                                                           const char *pLayerPrefix,
                                                           const char *pMessage,
                                                           void *pUserData) {
        (void) flags;
        (void) object;
        (void) location;
        (void) messageCode;
        (void) pUserData;
        (void) pLayerPrefix; // Unused arguments
        auto txt = GetDebugReportObjectTypeTxt(objectType);
        const auto log = std::format("[Vulkan 调试信息][{}]:\t\n{}\n", txt, pMessage);
        std::cerr << log;
//      Log::Error("[vulkan] Debug report from ObjectType: {}\nMessage:{}\n", (int) objectType, pMessage);
        return VK_FALSE;
    }

    inline static void CheckVkResult(VkResult err) {
        if (err == 0) return;
        auto txt = GetResultExt(err);
        const std::string log = std::format("[Vulkan 错误]:{}\n", txt);
        if (err < 0) throw std::runtime_error(log);
        else std::cerr << log;
        //Log::Error("[vulkan] Error: VkResult = {0}", (int) err);
    }

    inline static void CreateVkInstance(const VkAllocationCallbacks *allocator,
                                        std::vector<const char *> &instanceExtensions,
                                        VkInstance &instance,
                                        VkDebugReportCallbackEXT &debugReport) {
        VkResult err;
        VkApplicationInfo appInfo{};
        VkInstanceCreateInfo createInfo{};
        appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
        appInfo.pApplicationName = "Natural Engine Application";
        appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0);
        appInfo.pEngineName = "Natural Engine";
        appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0);
        appInfo.apiVersion = VK_API_VERSION_1_3;
        createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
        createInfo.pApplicationInfo = &appInfo;
        u32 properties_count;
        std::vector<VkExtensionProperties> properties{};
        err = vkEnumerateInstanceExtensionProperties(nullptr, &properties_count, nullptr);
        CheckVkResult(err);
        properties.resize(properties_count);
        err = vkEnumerateInstanceExtensionProperties(nullptr, &properties_count, properties.data());
        CheckVkResult(err);
        for (const VkExtensionProperties &p: properties) {
            if (strcmp(p.extensionName, VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME) == 0) {
                auto it = std::find(instanceExtensions.begin(), instanceExtensions.end(), VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME);
                if (it == instanceExtensions.end()) instanceExtensions.push_back(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME);
            }
#ifdef VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME
            else if (strcmp(p.extensionName, VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME) != 0) {
                auto it = std::find(instanceExtensions.begin(), instanceExtensions.end(), VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME);
                if (it == instanceExtensions.end()) {
                    instanceExtensions.push_back(VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME);
                    createInfo.flags |= VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR;
                }
            }
#endif
        }
#ifdef APP_USE_VULKAN_DEBUG_REPORT
        const char *layers[] = {"VK_LAYER_KHRONOS_validation"};
        createInfo.enabledLayerCount = 1;
        createInfo.ppEnabledLayerNames = layers;
        instanceExtensions.push_back("VK_EXT_debug_report");
#endif
        createInfo.enabledExtensionCount = (u32) instanceExtensions.size();
        createInfo.ppEnabledExtensionNames = instanceExtensions.data();
        err = vkCreateInstance(&createInfo, allocator, &instance);
        CheckVkResult(err);
#ifdef IMGUI_IMPL_VULKAN_USE_VOLK
        volkLoadInstance(_instance);
#endif
#ifdef APP_USE_VULKAN_DEBUG_REPORT
        auto createDebugReportCallbackEXT = (PFN_vkCreateDebugReportCallbackEXT) vkGetInstanceProcAddr(instance, "vkCreateDebugReportCallbackEXT");
        if (createDebugReportCallbackEXT == nullptr) throw std::runtime_error("vkGetInstanceProcAddr is nullptr");
        VkDebugReportCallbackCreateInfoEXT debugReportCi = {};
        debugReportCi.sType = VK_STRUCTURE_TYPE_DEBUG_REPORT_CALLBACK_CREATE_INFO_EXT;
        debugReportCi.flags = VK_DEBUG_REPORT_ERROR_BIT_EXT | VK_DEBUG_REPORT_WARNING_BIT_EXT | VK_DEBUG_REPORT_PERFORMANCE_WARNING_BIT_EXT;
        debugReportCi.pfnCallback = HandleDebugReport;
        debugReportCi.pUserData = nullptr;
        err = createDebugReportCallbackEXT(instance, &debugReportCi, allocator, &debugReport);
        CheckVkResult(err);
#endif
    }

    inline static void GetVkPhysicalDevice(const VkInstance &instance, VkPhysicalDevice &physicalDevice) {
        physicalDevice = nullptr;
        VkResult err;
        u32 gpuCount;
        err = vkEnumeratePhysicalDevices(instance, &gpuCount, nullptr);
        CheckVkResult(err);
        assert(gpuCount > 0);
        std::vector<VkPhysicalDevice> gpus{};
        gpus.resize(gpuCount);
        err = vkEnumeratePhysicalDevices(instance, &gpuCount, gpus.data());
        CheckVkResult(err);
        for (VkPhysicalDevice &device: gpus) {
            VkPhysicalDeviceProperties properties;
            vkGetPhysicalDeviceProperties(device, &properties);
            if (properties.deviceType != VkPhysicalDeviceType::VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU) continue;
            physicalDevice = device;
            return;
        }
        physicalDevice = gpuCount > 0 ? gpus[0] : nullptr;
    }

    inline static void GetVkQueueFamily(const VkPhysicalDevice &physicalDevice, u32 &queueFamily) {
        queueFamily = (u32) -1;
        u32 count = 0;
        vkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, &count, nullptr);
        std::vector<VkQueueFamilyProperties> queueFamilys(count);
        vkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, &count, queueFamilys.data());
        for (size_t i = 0; i < queueFamilys.size(); ++i) {
            const auto &queue = queueFamilys[i];
            if (queue.queueFlags & VK_QUEUE_GRAPHICS_BIT) {
                queueFamily = i;
                break;
            }
        }

        if (queueFamily == (u32) -1) {
            throw std::runtime_error("queueFamily is -1");
        }
    }

    inline static void GetVkDevice(const VkAllocationCallbacks *allocator, const VkPhysicalDevice &physicalDevice, u32 queueFamily, VkDevice &device, VkQueue &queue) {
        VkResult err;
        device = nullptr;
        queue = nullptr;
        std::vector<const char *> deviceExtensions = {
                VK_KHR_SWAPCHAIN_EXTENSION_NAME,
                VK_KHR_SURFACE_EXTENSION_NAME,
        };
        u32 propertiesCount;
        std::vector<VkExtensionProperties> properties{};
        err = vkEnumerateDeviceExtensionProperties(physicalDevice, nullptr, &propertiesCount, nullptr);
        CheckVkResult(err);
        properties.resize(propertiesCount);
        err = vkEnumerateDeviceExtensionProperties(physicalDevice, nullptr, &propertiesCount, properties.data());
        CheckVkResult(err);
#ifdef VK_KHR_PORTABILITY_SUBSET_EXTENSION_NAME
        if (IsExtensionAvailable(properties,VK_KHR_PORTABILITY_SUBSET_EXTENSION_NAME))
          deviceExtensions.push_back(VK_KHR_PORTABILITY_SUBSET_EXTENSION_NAME);
#endif
        const f32 queuePriority[] = {1.0f};
        std::vector<VkDeviceQueueCreateInfo> queueInfo = {
                {
                        VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO,
                        nullptr,
                        0,
                        queueFamily,
                        1,
                        queuePriority,
                }
        };
        VkDeviceCreateInfo createInfo = {
                VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO,
                nullptr,
                0,
                (u32) queueInfo.size(),
                queueInfo.data(),
                0,
                nullptr,
                (u32) deviceExtensions.size(),
                deviceExtensions.data(),
                nullptr,
        };

        err = vkCreateDevice(physicalDevice, &createInfo, allocator, &device);
        CheckVkResult(err);
        vkGetDeviceQueue(device, queueFamily, 0, &queue);
    }

    inline static void CreateDescriptorPool(const VkAllocationCallbacks *allocator, const VkDevice &device, VkDescriptorPool &descriptorPool) {
        VkResult err;
        std::vector<VkDescriptorPoolSize> poolSizes = {
                {VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1},
        };
        VkDescriptorPoolCreateInfo poolInfo = {
                VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO,
                nullptr,
                VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT,
                1,
                (u32) poolSizes.size(),
                poolSizes.data(),
        };
        err = vkCreateDescriptorPool(device, &poolInfo, allocator, &descriptorPool);
        CheckVkResult(err);
    }

    Graphics::Graphics() : _instanceExtensions(), _instance(nullptr),
                           _allocator(nullptr), _debugReport(nullptr),
                           _physicalDevice(nullptr), _queueFamily((u32) -1),
                           _device(nullptr), _queue(nullptr), _descriptorPool(nullptr) {
        CreateVkInstance(_allocator, _instanceExtensions, _instance, _debugReport);
        GetVkPhysicalDevice(_instance, _physicalDevice);
        GetVkQueueFamily(_physicalDevice, _queueFamily);
        GetVkDevice(_allocator, _physicalDevice, _queueFamily, _device, _queue);
        CreateDescriptorPool(_allocator, _device, _descriptorPool);
    }

    Graphics::~Graphics() {
        vkDestroyDescriptorPool(_device, _descriptorPool, _allocator);
#ifdef APP_USE_VULKAN_DEBUG_REPORT
        auto destroyDebugReportCallbackEXT = (PFN_vkDestroyDebugReportCallbackEXT) vkGetInstanceProcAddr(_instance, "vkDestroyDebugReportCallbackEXT");
        destroyDebugReportCallbackEXT(_instance, _debugReport, _allocator);
#endif
        vkDestroyDevice(_device, _allocator);
        vkDestroyInstance(_instance, _allocator);
    }

} // namespace NaturalEngine